
const LINE_SPLIT = "\n"
const sleep = (ms) => new Promise(res => setTimeout(res, ms))

export default class GcodeParser {

    points = []

    lastPoint = { x: 0, y: 0, z: 0, e: 0, type: "travel" }

    opts = {};

    read = 0

    _abort = false

    qualityRes = null;

    gcodeInfo = {};

    constructor(opts = {}) {
        this.opts = opts
    }

    abort = () => {
        this._abort = true
    }

    isMoveCmd(cmd) {
        return cmd.startsWith("G0") || cmd.startsWith("G1");
    }

    GPointFromCmd(cmd, prev) {
        let point = {...prev};
        cmd = cmd.split(";")[0]
        const tokens = cmd.split(/ +/g).map(t => t.toLocaleLowerCase())
        let key = '', val = 0;
        let hasExtruded = false;
        for (let i = 1; i < tokens.length; i++) {
            if (!tokens[i]) continue
            key = tokens[i][0];
            val = Number(tokens[i].slice(1));
            if (key == 'e' && val > 0) hasExtruded = true;
            point[key] = val;
        }
        point.type = hasExtruded ? "extrude" : "travel";
        return point;
    }

    async parseGcode(reader) {
        let prevCodeLine = "";
        this.read = 0
        while (!this._abort) {
            const chunk = await reader.read()
            if (!chunk) break
            this.read += chunk.length
            const codeLines = chunk.split(LINE_SPLIT)
            codeLines[0] = prevCodeLine + codeLines[0]
            prevCodeLine = codeLines.pop() || ""
            this.parseCodeLines(codeLines)
            await sleep(1)
        }
        if (this.opts.onFinished) this.opts.onFinished(this.gcodeInfo)
    }

    parseCodeLines(codeLines) {
        let isFinished = false;
        for (const line of codeLines) {
            if (this.isMoveCmd(line) && this.gcodeInfo.LAYER_COUNT && !isFinished) {

                const point = this.GPointFromCmd(line, this.lastPoint)
                
                if (point.z !== this.lastPoint.z) {
                    const layer = Math.ceil(this.lastPoint.z/Number(this.gcodeInfo.MINZ));
                    if (this.opts.onProgress && layer > 0) this.opts.onProgress(Object.assign(this.gcodeInfo, {layer}), this.points)
                    this.points = [];
                }

                this.points.push(point)

                this.lastPoint = Object.assign(this.lastPoint, point);
                
            } else if (line.startsWith(";") && line.includes(":")) {
                const props = line.replace(";", "").replace(/\s*/g,"").split(':');
                this.gcodeInfo[props[0]] = props[1];
                isFinished = line.startsWith(";End");
            }
        }
    }

}

